#include <msp430.h>
#include <stdint.h>
#include "hd44780.h"

#define HD44780_DADOS_PORTA        P1OUT
#define HD44780_DADOS_DIRECAO      P1DIR
#define HD44780_DADOS_PORTA_IN     P1IN

#define HD44780_CONTROLE_PORTA     P2OUT
#define HD44780_CONTROLE_DIRECAO   P2DIR

#define HD44780_RS          BIT0
#define HD44780_RW          BIT1
#define HD44780_EN          BIT2
#define HD44780_BUSY        BIT7
#define HD44780_DADOS        (BIT7 | BIT6 | BIT5 | BIT4)
#define HD44780_DADOS_SHIFT  4

#define formataDadosBarramento4Bits(dado) ((dado) << HD44780_DADOS_SHIFT)
#define getDadosBarramento4BitsFormatado() (HD44780_DADOS_PORTA_IN >> HD44780_DADOS_SHIFT);


static void _escreve4bits(uint8_t dado);
static void _escreve8bits(uint8_t dado);
static uint8_t _getFlagBusy(void);
static uint8_t _le4bits(void);
static uint8_t _le8bits(void);

void hd44780_inicializa(void){
  HD44780_CONTROLE_DIRECAO |= (HD44780_RS | HD44780_RW | HD44780_EN);
  HD44780_DADOS_DIRECAO |= HD44780_DADOS;

  HD44780_DADOS_PORTA &= ~HD44780_DADOS;
  HD44780_CONTROLE_PORTA &= ~(HD44780_RS | HD44780_RW | HD44780_EN);


  // Utiliza barramento com 4 pinos
  _escreve4bits(0x2);
  
  // Limpa o display
  _escreve8bits(0x28);
  hd44780_enviaComando(HD44780_CMD_CLEAR_DISPLAY);
}

void hd44780_enviaComando(uint8_t comando){
  while (_getFlagBusy());
  HD44780_CONTROLE_PORTA &= ~HD44780_RS;
  _escreve8bits(comando);
}

void hd44780_escreveCaractere(char caractere){
  while (_getFlagBusy());
  HD44780_CONTROLE_PORTA |= HD44780_RS;
  _escreve8bits(caractere);
}

void hd44780_escreveString(char* str){
    while (*str != '\0') hd44780_escreveCaractere(*str++);
}

static void _escreve4bits(uint8_t dado){

  // Espera a temporizacao
  _NOP(); _NOP();  _NOP(); _NOP(); _NOP(); _NOP();

  // no need to wait 60ns for t_AS, the function call provides the delay
  HD44780_CONTROLE_PORTA |= HD44780_EN;
  HD44780_DADOS_PORTA |= formataDadosBarramento4Bits(dado);
  
  // Espera a temporizacao
  _NOP(); _NOP();  _NOP(); _NOP(); _NOP(); _NOP();
  _NOP(); _NOP();  _NOP(); _NOP(); _NOP(); _NOP();

  HD44780_CONTROLE_PORTA &= ~HD44780_EN;
  
  HD44780_DADOS_PORTA &= ~HD44780_DADOS;

  // Espera a temporizacao
  _NOP(); _NOP(); _NOP(); _NOP(); _NOP(); _NOP(); _NOP(); _NOP();
  _NOP(); _NOP(); _NOP(); _NOP(); _NOP(); _NOP(); _NOP(); _NOP();

}

static void _escreve8bits(uint8_t dado){
  HD44780_DADOS_DIRECAO |= HD44780_DADOS;
  HD44780_CONTROLE_PORTA &= ~HD44780_RW;
  _escreve4bits(dado>>HD44780_DADOS_SHIFT);
  _escreve4bits(dado & 0x0f);

}

static uint8_t _getFlagBusy(void){
  HD44780_CONTROLE_PORTA &= ~HD44780_RS;
  HD44780_DADOS_PORTA &= ~HD44780_DADOS_PORTA;
  
  return _le8bits() & 0x80;
}

static uint8_t _le4bits(void){
  // Espera a temporizacao
  _NOP(); _NOP();  _NOP(); _NOP(); _NOP(); _NOP();

   // no need to wait 60ns for t_AS, the function call provides the delay
  HD44780_CONTROLE_PORTA |= HD44780_EN;

  // Espera a temporizacao
  _NOP(); _NOP(); _NOP(); _NOP(); _NOP(); _NOP();
  _NOP(); _NOP(); _NOP(); _NOP(); _NOP(); _NOP();

  uint8_t dado = getDadosBarramento4BitsFormatado(); // Data is valid, read 4bits
  HD44780_CONTROLE_PORTA &= ~HD44780_EN;

  // Satisfy T_cycE requirement (this is probably overkill)
  _NOP(); _NOP(); _NOP(); _NOP(); _NOP(); _NOP(); _NOP(); _NOP();
  _NOP(); _NOP(); _NOP(); _NOP(); _NOP(); _NOP(); _NOP(); _NOP();

  return dado;

}

static uint8_t _le8bits(void){
    uint8_t dado;
    HD44780_DADOS_DIRECAO &= ~HD44780_DADOS;
    HD44780_CONTROLE_PORTA |= HD44780_RW;
    dado = _le4bits() << 4;
    dado |= _le4bits();
    return dado;
}

